/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Schnittstellen;

import Einheiten.Einheit;
import Schnittstellen.Audio.AudioFile;
import Schnittstellen.Audio.Sound;
import Schnittstellen.Audio.Soundset;
import Schnittstellen.Graphisches.Modelle.JmeModell;
import Schnittstellen.Graphisches.Modelle.Modell;
import Schnittstellen.Graphisches.Texturen.BildTextur;
import Schnittstellen.Graphisches.Texturen.Textur;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.channels.FileChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Geez
 */
public class ObjectManager {

    private static ObjectManager instance;
    private Bank<Sound> soundbank;
    private Bank<Soundset> soundsetbank;
    private Bank<Modell> modellbank;
    private Bank<Textur> texturbank;
    private Bank<Einheit> einheitenbank;

    /**
     * 
     */
    public enum Banktyp {

        /**
         * Banktyp SoundBank
         */
        SOUNDBANK,
        /**
         * Banktyp SoundsetBank
         */
        SOUNDSETBANK,
        /**
         * Banktyp ModellBank
         */
        MODELLBANK,
        /**
         * Banktyp TexturBank
         */
        TEXTURBANK,
        /**
         * Banktyp EinheitenBank
         */
        EINHEITENBANK
    }

    /**
     * Behandlungsart einer uebergebenen datei entweder als modell,
     * sound, textur oder soundset
     */
    public enum Dateityp {

        /**
         *  Dateityp Modell
         */
        MODELL,
        /**
         * Dateityp Sound
         */
        SOUND,
        /**
         * Dateityp Textur
         */
        TEXTUR,
        /**
         * Dateityp Soundset
         */
        SOUNDSET
    }

    /**
     * Singleton daher privater Konstruktor
     */
    private ObjectManager() {
        soundbank = new Bank<Sound>();
        soundbank.setBankTyp(Banktyp.SOUNDBANK);
        soundsetbank = new Bank<Soundset>();
        soundsetbank.setBankTyp(Banktyp.SOUNDSETBANK);
        modellbank = new Bank<Modell>();
        modellbank.setBankTyp(Banktyp.MODELLBANK);
        texturbank = new Bank<Textur>();
        texturbank.setBankTyp(Banktyp.TEXTURBANK);
        einheitenbank = new Bank<Einheit>();
        einheitenbank.setBankTyp(Banktyp.EINHEITENBANK);
    }

    /**
     * Singleton um die derzeitige Instanz des ObjectManager zu bekommen.
     * Ist keine vorhanden, so wird eine neue angelegt
     * @return die derzeitig laufende Instanz des ObjektManager
     */
    public static ObjectManager getInstance() {
        if (instance == null) {
            instance = new ObjectManager();
            instance.loadAssets();
        }
        return instance;
    }

    /**
     * gibt die ModellSpeicherbank zurueck
     * @return die ModellSpeicherbank
     */
    public Bank<Modell> getModellbank() {
        return modellbank;
    }

    /**
     * gibt die SoundSpeicherbank zurueck
     * @return die soundSpeicherbank
     */
    public Bank<Sound> getSoundbank() {
        return soundbank;
    }

    /**
     * gibt die SoundsetSpeicherbank zurueck
     * @return die soundsetSpeicherbank
     */
    public Bank<Soundset> getSoundsetbank() {
        return soundsetbank;
    }

    /**
     * gibt die TexturSpeicherbank zurueck
     * @return die TexturSpeicherbank
     */
    public Bank<Textur> getTexturbank() {
        return texturbank;
    }

    /**
     * gibt die EinheitenSpeicherbank zuruck
     * @return die EinheitenSpeicherbank
     */
    public Bank<Einheit> getEinheitenbank() {
        return einheitenbank;
    }

    /**
     * Fuegt der EinheitenSpeicherbank eine neue Einheit hinzu. Existiert bereits
     * eine Einheit mit gleichem Editornamen, so wird diese ueberschrieben.
     * @param e die Einheit die hinzugefuegt werden soll
     * @return wahr, wenn die Einheit hinzugefuegt werden konnte
     */
    public boolean addEinheit(Einheit e) {
        return einheitenbank.add(e);
    }

    /**
     * Fuegt der SoundsetSpeicherbank ein neues Soundset hinzu. Existiert bereits
     * ein Soundset mit gleichem Editornamen, so wird dieses ueberschrieben.
     * @param soundset das Soundset, dass hinzugefuegt werden soll
     * @return wahr, wenn das Soundset hinzugefuegt werden konnte
     */
    public boolean addSoundset(Soundset soundset) {
        return soundsetbank.add(soundset);
    }

    /**
     * Fuegt der SoundsetSpeicherbank ein neues Soundset hinzu. Existiert bereits
     * ein Model mit gleichem Editornamen, so wird dieses ueberschrieben.
     * @param model das Model, dass hinzugefuegt werden soll
     * @return wahr, wenn das Model hinzugefuegt werden konnte
     */
    public boolean addModel(Modell model) {
        return modellbank.add(model);
    }

    /**
     * Fuegt der SoundSpeicherbank einen neuen Sound hinzu. Existiert bereits
     * ein Sound mit gleichem Editornamen, so wird dieser ueberschrieben.
     * @param sound der Sound, der hinzugefuegt werden soll
     * @return wahr, wenn der Sound hinzugefuegt werden konnte
     */
    public boolean addSound(Sound sound) {
        return soundbank.add(sound);
    }

    /**
     * Fuegt der Texturbank eine neue Textur hinzu. Existiert bereits
     * eine Textur mit gleichem Editornamen, so wird diese ueberschrieben.
     * @param textur die Textur die hinzugefuegt werden soll
     * @return wahr, wenn die Textur hinzugefuegt werden konnte
     */
    public boolean addTextur(Textur textur) {
        return texturbank.add(textur);
    }

    /**
     * Speichert die uebergebene Bank unter dem gegebenen Pfad ab.
     * @param bank die zu speichernde Bank
     * @param path der Pfad der Datei
     * @return wahr, wenn der Vorgang reibungslos ablief
     */
    public boolean saveBank(Bank bank, File path) {
        if (path == null || bank == null) {
            throw new IllegalArgumentException();
        }
        if (bank.getBankTyp() == null) {
            throw new RuntimeException("no Banktyp defined");
        }
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(path));
            oos.writeObject(bank);
            oos.flush();
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
            return false;
        } finally {
            try {
                oos.close();
            } catch (IOException ex) {
                System.err.println(ex.getMessage());
                return false;
            }
        }
        return true;
    }

    /**
     * laed aus dem gegebenen Pfad eine Speicherbank raus und ersetzt die jeweilige
     * bisher verwendete Speicherbank mit der neu geladenen.
     * @param path der Pfad zu der zu ladenden datei
     * @return wahr, falls die Speicherbank geladen werden konnte.
     */
    public boolean loadBank(File path) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(path));
            Object o = ois.readObject();
            if (o instanceof Bank) {
                Bank b = (Bank) o;
                switch (b.getBankTyp()) {
                    case EINHEITENBANK: {
                        this.einheitenbank = b;
                        break;
                    }
                    case MODELLBANK: {
                        this.modellbank = b;
                        break;
                    }
                    case SOUNDBANK: {
                        this.soundbank = b;
                        break;
                    }
                    case SOUNDSETBANK: {
                        this.soundsetbank = b;
                        break;
                    }
                    case TEXTURBANK: {
                        this.texturbank = b;
                        break;
                    }
                }
            } else {
                System.out.println("fail: " + o);
                return false;
            }
        } catch (ClassNotFoundException ex) {
            System.err.println(ex.getMessage());
            return false;
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
            return false;
        } finally {
            try {
                ois.close();
            } catch (IOException ex) {
                System.err.println(ex.getMessage());
                return false;
            }
        }
        return true;
    }

    /**
     * laed die Assets der Models, Sounds und Textures ordner
     */
    public void loadAssets() {
        loadModels(new File("assets/Models"));
        loadSounds(new File("assets/Sounds"));
        loadTextures(new File("assets/Textures"));
    }

    /**
     * laed eine datei die den uebergebenen pfad hat in den assets ordner.
     * hierbei muss ein dateityp angegeben werden, damit der importprozess 
     * reibungslos ablaufen kann
     * @param d der dateityp der zu importierenden datei
     * @param pfad der pfad zur datei
     * @return wahr, falls der importprozess reibungslos ablief
     */
    public boolean addToAssets(Dateityp d, File pfad) {
        if (pfad == null) {
            throw new IllegalArgumentException();
        }
        switch (d) {
            case MODELL: {
                File pathMat = null;
                if (pfad.getName().endsWith(".xml")) {
                    pathMat = new File(pfad.getPath().substring(0, pfad.getPath().length() - 3) + "material");
                } else if (pfad.getName().endsWith(".obj")) {
                    pathMat = new File(pfad.getPath().substring(0, pfad.getPath().length() - 3) + "mtl");
                }
                if (pathMat == null || !pathMat.exists()) {
                    throw new IllegalArgumentException(pathMat.getAbsolutePath() + " does not exist");
                }
                File out = new File("assets/Models/" + pfad.getName().substring(0, pfad.getName().length() - 4)).getAbsoluteFile();
                out.mkdir();
                try {
                    copyFile(pfad, new File(out, pfad.getName()));
                    copyFile(pathMat, new File(out, pathMat.getName()));
                } catch (IOException ex) {
                    Logger.getLogger(JmeModell.class.getName()).log(Level.SEVERE, null, ex);
                    return false;
                }
                return true;
            }
            case SOUND: {
                File out = new File("assets/Sounds/", pfad.getName());
                try {
                    copyFile(pfad, out);
                } catch (IOException ex) {
                    Logger.getLogger(ObjectManager.class.getName()).log(Level.SEVERE, null, ex);
                    return false;
                }
                return true;
            }
            case TEXTUR: {
                File out = new File("assets/Textures/", pfad.getName());
                try {
                    copyFile(pfad, out);
                } catch (IOException ex) {
                    Logger.getLogger(ObjectManager.class.getName()).log(Level.SEVERE, null, ex);
                    return false;
                }
                return true;
            }
            default: {
                return false;
            }
        }

    }

    private void loadModels(File path) {

        if (path.isDirectory()) {
            for (File f : path.listFiles()) {
                loadModels(f);
            }
        } else if (path.isFile()) {
            if (path.getAbsolutePath().toLowerCase().endsWith(".obj") || path.getAbsolutePath().toLowerCase().endsWith(".xml")) {
                this.addModel(new JmeModell(path.getName()));
            }
        }
    }

    private void loadSounds(File path) {
        if (path.isDirectory()) {
            for (File f : path.listFiles()) {
                loadSounds(f);
            }
        } else if (path.isFile()) {
            if (path.getAbsolutePath().toLowerCase().endsWith("wav") || path.getAbsolutePath().toLowerCase().endsWith("aiff")) {
                this.addSound(new AudioFile(path.getName()));
            }
        }
    }

    private void loadTextures(File path) {
        if (path.isDirectory()) {
            for (File f : path.listFiles()) {
                loadTextures(f);
            }
        } else if (path.isFile()) {
            if (path.getAbsolutePath().toLowerCase().endsWith("jpg") || path.getAbsolutePath().toLowerCase().endsWith("jpeg")
                    || path.getAbsolutePath().toLowerCase().endsWith("png") || path.getAbsolutePath().toLowerCase().endsWith("gif")) {
                this.addTextur(new BildTextur(path.getName()));
            }
        }
    }

    /**
     * loescht eine datei aus dem assetsordner
     * @param d der datentyp der datei
     * @param object das objekt das geloescht werden soll
     * @return wahr, falls die datei geloescht werden konnte
     */
    public boolean removeFromAssets(Dateityp d, RTSObject object) {
        switch (d) {
            case MODELL: {
                String name = object.getEditorName();
                File path = new File("assets/Models/" + name.substring(0, name.length() - 4));
                if (path.exists() && path.isDirectory()) {
                    for (File f : path.listFiles()) {
                        f.delete();
                    }
                    path.delete();
                    return modellbank.remove((Modell) object);
                }
            }
            case SOUND: {
                String name = object.getEditorName();
                File path = new File("assets/Sounds/" + name);
                if (path.exists()) {
                    path.delete();
                    return soundbank.remove((Sound) object);
                }
            }
            case TEXTUR: {
                String name = object.getEditorName();
                File path = new File("assets/Textures/" + name);
                if (path.exists()) {
                    path.delete();
                    return texturbank.remove((Textur) object);
                }
                return false;
            }

            default: {
                return false;
            }
        }
    }

    /**
     * kopiert eine datei an einen anderen ort
     * @param in die quelldatei
     * @param out die zieldatei
     * @throws IOException 
     */
    public static void copyFile(File in, File out) throws IOException {
        out.setWritable(true);
        FileChannel inChannel = new FileInputStream(in).getChannel();
        FileChannel outChannel = new FileOutputStream(out).getChannel();
        try {
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            throw e;
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }
}
